#include "glwidget.h"

GLWidget::GLWidget(QWidget *parent) :
  QGLWidget(parent)
{
  connect(&timer, SIGNAL(timeout()), this, SLOT(UpdateScene()));
  timer.setInterval(25);
  timer.start();
  state = STOP;
  player_rotation = 0;
}

GLWidget::~GLWidget()
{
}

void GLWidget::initializeGL()
{
  glClearColor(0.1f, 0.1f, 0.1f, 1.0f);

  glCullFace(GL_BACK);
  glEnable(GL_CULL_FACE);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);

  // Fog test
//  glEnable(GL_FOG);
//  glFogi(GL_FOG_MODE, GL_EXP2);
//  GLfloat fogColor[4] = {0.5, 0.5, 0.5, 1.0};
//  glFogfv(GL_FOG_COLOR, fogColor);
//  glFogf(GL_FOG_DENSITY, 0.5);
//  glHint(GL_FOG_HINT, GL_NICEST);

  // Afegiu totes les inicialitzacions que necessiteu,
  // tant de l'escena com de la camera

  lighting.initial.active = true;
  lighting.initial.color[0] = 1.0f;
  lighting.initial.color[1] = 1.0f;
  lighting.initial.color[2] = 1.0f;
  lighting.initial.color[3] = 0.0f;

  lighting.scene.color[0] = 0.6f;
  lighting.scene.color[1] = 0.6f;
  lighting.scene.color[2] = 0.6f;
  lighting.scene.color[3] = 0.0f;

  lighting.player.color[0] = 0.6f;
  lighting.player.color[1] = 0.6f;
  lighting.player.color[2] = 0.0f;
  lighting.player.color[3] = 0.0f;
  lighting.player.cutoff = 60.f;
  lighting.player.exponent = 2.f;

  cam.SetContent(scene.GetCenter(), scene.GetRadius());
  cam.SetPlayer(scene.GetPlayerPosition(), scene.GetPlayerOrientation());
  lighting.SetSceneLight(scene.GetCenter());
  lighting.SetPlayerLight(scene.GetPlayerPosition(), scene.GetPlayerOrientation());

  culling_enabled = true;
}

void GLWidget::resizeGL (int width, int height)
{
  glViewport (0, 0, width, height);
  cam.SetAspectRatio((float)width / (float)height);
}

void GLWidget::paintGL( void )
{
  // Esborrem els buffers
  switch (state) {
  case STOP:
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    break;
  case PLAY:
    glClearColor(0.3f, 0.6f, 0.9f, 1.0f);
    break;
  }

  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

  // posar la projeccio
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(cam.GetFOV(), cam.GetAspectRatio(), cam.GetNear(), cam.GetFar());

  // transformacions de la camera
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  Point pan = cam.GetPan();
  Point vrp = cam.GetVrp();
  glTranslatef(0, 0, -cam.GetDistance()); // Allunyar la camera la distancia adequada
  //glTranslatef(-pan.x, -pan.y, -pan.z); // Fer pan respecte els eixos de la camera
  glRotatef(cam.GetTheta(), 1, 0, 0); // Rotacio vertical
  glRotatef(cam.GetPhi(), 0, 1, 0); // Rotacio horitzontal
  glTranslatef(-pan.x, -pan.y, -pan.z); // Fer pan respecte el VRP
  glTranslatef(-vrp.x, -vrp.y, -vrp.z); // Centrar la camera per fer les rotacions

  // dibuixar eixos aplicacio
  glDisable(GL_LIGHTING);
  glBegin(GL_LINES);
  glColor3f(1,0,0); glVertex3f(0,0,0); glVertex3f(20,0,0); // X
  glColor3f(0,1,0); glVertex3f(0,0,0); glVertex3f(0,20,0); // Y
  glColor3f(0,0,1); glVertex3f(0,0,0); glVertex3f(0,0,20); // Z
  glEnd();

  // dibuixar la fletxa cap on mira el jugador
  if (state == STOP && scene.IsPlayerAlive()) {
    Point p = scene.GetPlayerPosition();
    Direction d = scene.GetPlayerOrientation();
    glPushMatrix();
    glTranslatef(p.x, p.y, p.z);
    switch (d) {
    case NORTH:
      break;
    case WEST:
      glRotatef(90, 0, 1, 0);
      break;
    case SOUTH:
      glRotatef(180, 0, 1, 0);
      break;
    case EAST:
      glRotatef(270, 0, 1, 0);
      break;
    default:
      break;
    }
    glBegin(GL_LINES);
    glColor3f(1, 1, 1);
    glVertex3f(1, 0.5, 0); glVertex3f(0, 0.5, 0);
    glVertex3f(1, 0.5, 0); glVertex3f(0.5, 0.5, +0.5);
    glVertex3f(1, 0.5, 0); glVertex3f(0.5, 0.5, -0.5);
    glEnd();
    glPopMatrix();
  }

  // Lighting
  if (lighting.IsEnabled()) {

    if (lighting.initial.active) {
      glEnable(GL_LIGHT0);
      if (lighting.initial.changed) {
        glLightfv(GL_LIGHT0, GL_DIFFUSE, lighting.initial.color);
        glLightfv(GL_LIGHT0, GL_SPECULAR, lighting.initial.color);
        lighting.initial.changed = false;
      }
    } else {
      glDisable(GL_LIGHT0);
    }

    if (lighting.scene.active) {
      glEnable(GL_LIGHT1);
      glLightfv(GL_LIGHT1, GL_POSITION, lighting.scene.position);
      if (lighting.scene.changed) {
        glLightfv(GL_LIGHT1, GL_DIFFUSE, lighting.scene.color);
        glLightfv(GL_LIGHT1, GL_SPECULAR, lighting.scene.color);
        glLightfv(GL_LIGHT1, GL_AMBIENT, lighting.scene.ambient);
        lighting.scene.changed = false;
      }
      // Draw scene light box before enabling lighting
      Point box[2];
      lighting.scene.GenerateLightBox(box);
      glColor3fv(lighting.scene.color);
      Box::Render(box[0], box[1]);
    } else {
      glDisable(GL_LIGHT1);
    }

    if (lighting.player.active && scene.IsPlayerAlive()) {
      glEnable(GL_LIGHT2);
      glLightfv(GL_LIGHT2, GL_POSITION, lighting.player.position);
      if (lighting.player.changed) {
        glLightfv(GL_LIGHT2, GL_SPOT_DIRECTION, lighting.player.direction);
        glLightfv(GL_LIGHT2, GL_DIFFUSE, lighting.player.color);
        glLightfv(GL_LIGHT2, GL_SPECULAR, lighting.player.color);
        glLightfv(GL_LIGHT2, GL_AMBIENT, lighting.player.ambient);
        glLightf(GL_LIGHT2, GL_SPOT_CUTOFF, lighting.player.cutoff);
        glLightf(GL_LIGHT2, GL_SPOT_EXPONENT, lighting.player.exponent);
        lighting.scene.changed = false;
      }
    } else {
      glDisable(GL_LIGHT2);
    }
    glEnable(GL_LIGHTING);
  }

  // dibuixar la resta de l'escena
  scene.Render(!cam.IsFpv(), player_rotation);

  //std::cout << "Rendering(" << rand()%10 << ")" << std::endl;
}

void GLWidget::mousePressEvent( QMouseEvent *e)
{
  xClick = e->x();
  yClick = e->y();

  if (e->button()&Qt::LeftButton && !(e->modifiers()&(Qt::ShiftModifier|Qt::AltModifier|Qt::ControlModifier)))
  {
    DoingInteractive = ROTATE;
  }
  else if (e->button()&Qt::LeftButton &&  e->modifiers() &Qt::ShiftModifier)
  {
    DoingInteractive = ZOOM;
  }
  else if (e->button()&Qt::LeftButton &&  e->modifiers() &Qt::ControlModifier)
  {
    DoingInteractive = PAN;
  }
  else if(e->button()&Qt::MiddleButton || e->button()&Qt::RightButton) {

    // Render the scene with the pick colors
    glDisable(GL_LIGHTING);
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    e->button()&Qt::MiddleButton ? scene.PickRender() : scene.EnemyPickRender();

    // Read the colour of the pixel where the mouse was clicked
    GLint viewport[4];
    GLubyte pixel[3];
    glGetIntegerv(GL_VIEWPORT, viewport);
    glReadPixels(xClick, viewport[3] - yClick, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, (void *)pixel);

    // Change the block with the coordinates indicated by the color (red equals the x coordinate of the block, and green the z coordinate)
    if (e->button()&Qt::MiddleButton && pixel[2] == 0) {
      scene.Click(pixel[0], pixel[1]);
    }
    else if (e->button()&Qt::RightButton && pixel[1] == 0 && pixel[2] == 0) {
      scene.RemoveEnemy(pixel[0]);
    }

    // Render the scene again
    if (lighting.IsEnabled()) glEnable(GL_LIGHTING);
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    scene.Render(!cam.IsFpv(), player_rotation);
  }
}

void GLWidget::keyPressEvent(QKeyEvent *e)
{
  switch (e->key())
  {

  case Qt::Key_C:
    SwitchCamera();
    break;

  case Qt::Key_F:
    glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
    break;

  case Qt::Key_S:
    glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
    break;

  case Qt::Key_X:
    if (state == PLAY) scene.Shoot();
    break;

  case Qt::Key_Up:
    if (state == PLAY) scene.MovePlayer(FRONT);
    break;

  case Qt::Key_Left:
    if (state == PLAY) scene.MovePlayer(LEFT);
    break;

  case Qt::Key_Right:
    if (state == PLAY) scene.MovePlayer(RIGHT);
    break;

  case Qt::Key_Down:
    if (state == PLAY) scene.MovePlayer(BACK);
    break;

  default:
    e->ignore(); break;
  }
  cam.SetPlayer(scene.GetPlayerPosition(), scene.GetPlayerOrientation());
  lighting.SetPlayerLight(scene.GetPlayerPosition(), scene.GetPlayerOrientation());
  updateGL();
}

void GLWidget::mouseReleaseEvent( QMouseEvent *)
{
  DoingInteractive = NONE;
}

void GLWidget::mouseMoveEvent(QMouseEvent *e)
{
  // Aqui cal que es calculi i s'apliqui la rotacio, el zoom o el pan
  // com s'escaigui...

  float x_incr = (e->x() - xClick);
  float y_incr = (e->y() - yClick);

  if (DoingInteractive == ROTATE)
  {
    cam.Rotate(x_incr / 3.f, y_incr / 3.f);
  }
  else if (DoingInteractive == ZOOM)
  {
    cam.Zoom(-y_incr / 100.f);
  }
  else if (DoingInteractive == PAN)
  {
    cam.Pan(-x_incr / 32.f, y_incr / 32.f);
  }
  xClick = e->x();
  yClick = e->y();
  updateGL();
}

void GLWidget::SwitchCulling()
{
  culling_enabled = !culling_enabled;
  if (culling_enabled) {
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
  }
  else {
    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
  }
  updateGL();
}

void GLWidget::SwitchDestruction() {
  scene.SwitchDestruction();
}

bool GLWidget::SwitchGame()
{
  switch (state) {
  case STOP:
    if (scene.IsReady()) {
      state = PLAY;
      updateGL();
      return true;
    }
    break;
  case PLAY:
    state = STOP;
    break;
  }
  updateGL();
  return false;
}

void GLWidget::SwitchCamera()
{
  cam.SwitchCamera();
  cam.Reset();
  updateGL();
}

void GLWidget::ResetCamera()
{
  cam.Reset();
  updateGL();
}

void GLWidget::SwitchInitialLight()
{
  lighting.initial.Switch();
  updateGL();
}

void GLWidget::SwitchSceneLight()
{
  lighting.scene.Switch();
  updateGL();
}

void GLWidget::SwitchPlayerLight()
{
  lighting.player.Switch();
  updateGL();
}

void GLWidget::InitialLightOptionsWidget()
{
  lighting.initial.ShowOptions();
}

void GLWidget::SceneLightOptionsWidget()
{
  lighting.scene.ShowOptions();
}

void GLWidget::PlayerLightOptionsWidget()
{
  lighting.player.ShowOptions();
}

void GLWidget::SceneLightPositionX(int x)
{
  lighting.scene.x = x;
  lighting.SetSceneLight(scene.GetCenter());
  updateGL();
}

void GLWidget::SceneLightPositionZ(int z)
{
  lighting.scene.z = z;
  lighting.SetSceneLight(scene.GetCenter());
  updateGL();
}

void GLWidget::SetMapSize(int size)
{
  scene.GenerateMaze(size);
  cam.SetContent(scene.GetCenter(), scene.GetRadius());
  cam.SetPlayer(scene.GetPlayerPosition(), scene.GetPlayerOrientation());
  lighting.SetSceneLight(scene.GetCenter());
  lighting.SetPlayerLight(scene.GetPlayerPosition(), scene.GetPlayerOrientation());
  updateGL();
}

void GLWidget::CarregaPersonatge()
{
  // Aqui s'ha de carregar el personatge.
  QString fileName = QFileDialog::getOpenFileName(this);
  scene.LoadPlayerModel(fileName);
  cam.SetPlayer(scene.GetPlayerPosition(), scene.GetPlayerOrientation());
  lighting.SetPlayerLight(scene.GetPlayerPosition(), scene.GetPlayerOrientation());
  updateGL();
}

void GLWidget::RotateCharacter(int rotation) {
  player_rotation = rotation;
  updateGL();
}

void GLWidget::CarregaEnemics()
{
  QString fileName = QFileDialog::getOpenFileName(this);
  scene.LoadEnemyModel(fileName);
  updateGL();
}

void GLWidget::SetEnemyCount(int count)
{
  scene.SetEnemyCount(count);
}

void GLWidget::UpdateScene()
{
  if (state == PLAY) {
    scene.Update();
    if (!scene.IsReady()) {
      cam.SetSceneCamera();
      state = STOP;
    }
    updateGL();
  }
  if (lighting.IsChanged()) {
    updateGL();
  }
}

void GLWidget::closeEvent(QCloseEvent *event) {
  lighting.HideOptions();
  event->accept();
}

void GLWidget::ResetGame()
{
  scene.Reset();
  state = STOP;
  updateGL();
}
